using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using FytSoa.Domain.Sys;
using FytSoa.Sugar;
using SqlSugar;
using Microsoft.AspNetCore.Mvc;
using System.Security.Cryptography;
using FytSoa.Application.Sys.Dto;
using FytSoa.Common.Param;
using FytSoa.Common.Result;
using Mapster;
using Microsoft.AspNetCore.Authorization;

namespace FytSoa.Application.Sys;

/// <summary>
/// 用户账号相关服务。
/// </summary>
[ApiExplorerSettings(GroupName = "v1"),AllowAnonymous]
public class SysUserAccountService : IApplicationService
{
    private readonly SugarRepository<SysUserAccount> _accounts;
    private readonly SugarRepository<SysUserSession> _sessions;
    private readonly SugarRepository<SysPasswordResetToken> _resetTokens;
    private readonly SugarRepository<SysLoginAudit> _loginAudits;
    private readonly SugarRepository<SysUserRole> _userRole;

    /// <summary>
    /// 用户
    /// </summary>
    /// <param name="db"></param>
    public SysUserAccountService(ISqlSugarClient db)
    {
        _accounts = new SugarRepository<SysUserAccount>(db);
        _sessions = new SugarRepository<SysUserSession>(db);
        _resetTokens = new SugarRepository<SysPasswordResetToken>(db);
        _loginAudits = new SugarRepository<SysLoginAudit>(db);
        _userRole = new SugarRepository<SysUserRole>(db);
    }

    /// <summary>
    /// 查询指定租户下的全部账号。
    /// </summary>
    public Task<List<SysUserAccount>> GetByTenantAsync(long tenantId) =>
        _accounts.AsQueryable()
            .Where(u => u.TenantId == tenantId)
            .OrderBy(u => u.Id)
            .ToListAsync();

    /// <summary>
    /// 分页查询账号。
    /// </summary>
    public async Task<PageResult<SysUserAccount>> GetAccountPageAsync(
        PageParam param)
    {
        var query = _accounts.AsQueryable();
        return  await query
            .OrderBy(u => u.Id)
            .ToPageAsync(param.Page, param.Limit);
    }

    /// <summary>
    /// 按用户名获取账号。
    /// </summary>
    public async Task<SysUserAccount?> FindByUserNameAsync(long tenantId, string userName) =>
        await _accounts.AsQueryable()
            .Where(u => u.TenantId == tenantId && u.UserName == userName)
            .FirstAsync();

    /// <summary>
    /// 创建账号并返回生成的ID。
    /// </summary>
    public async Task<long> CreateAsync(SysUserAccountDto dto)
    {
        ArgumentNullException.ThrowIfNull(dto);
        if (string.IsNullOrWhiteSpace(dto.UserName))
        {
            throw new ArgumentException("UserName is required.", nameof(dto));
        }
        dto.UserName = dto.UserName.Trim();
        if (string.IsNullOrWhiteSpace(dto.Password))
        {
            throw new ArgumentException("Password is required.", nameof(dto));
        }

        dto.Email = string.IsNullOrWhiteSpace(dto.Email) ? null : dto.Email?.Trim();

        var duplicate = await _accounts.AsQueryable()
            .Where(u => u.TenantId == dto.TenantId && (u.UserName == dto.UserName || (!string.IsNullOrEmpty(dto.Email) && u.Email == dto.Email)))
            .AnyAsync();
        if (duplicate)
        {
            throw new InvalidOperationException($"User '{dto.UserName}' already exists in tenant {dto.TenantId}.");
        }

        var account = dto.Adapt<SysUserAccount>();
        var (hash, algorithm) = HashPassword(dto.Password);
        var nowUtc = DateTime.UtcNow;

        account.PasswordHash = hash;
        account.PasswordAlgo = algorithm;
        account.PasswordUpdatedAt = nowUtc;
        account.CreatedAt = nowUtc;
        account.UpdatedAt = nowUtc;
        account.MustChangePassword = dto.MustChangePassword;

        return await _accounts.InsertReturnIdentityAsync(account);
    }

    /// <summary>
    /// 更新账号信息。
    /// </summary>
    public async Task<bool> UpdateAsync(SysUserAccountDto dto)
    {
        ArgumentNullException.ThrowIfNull(dto);
        var existing = await _accounts.GetByIdAsync(dto.Id)
                       ?? throw new InvalidOperationException($"User account {dto.Id} does not exist.");

        dto.Email = string.IsNullOrWhiteSpace(dto.Email) ? null : dto.Email?.Trim();

        if (!string.IsNullOrWhiteSpace(dto.Email))
        {
            var emailExists = await _accounts.AsQueryable()
                .Where(u => u.TenantId == existing.TenantId && u.Id != existing.Id && u.Email == dto.Email)
                .AnyAsync();
            if (emailExists)
            {
                throw new InvalidOperationException($"Email '{dto.Email}' already exists in tenant {existing.TenantId}.");
            }
        }

        var originalUserName = existing.UserName;
        var originalCreatedAt = existing.CreatedAt;
        var originalTenantId = existing.TenantId;

        dto.Adapt(existing);

        existing.UserName = originalUserName;
        existing.CreatedAt = originalCreatedAt;
        existing.TenantId = originalTenantId;

        var nowUtc = DateTime.UtcNow;

        if (!string.IsNullOrWhiteSpace(dto.Password))
        {
            var (hash, algorithm) = HashPassword(dto.Password);
            existing.PasswordHash = hash;
            existing.PasswordAlgo = algorithm;
            existing.PasswordUpdatedAt = nowUtc;

            // 重置登录失败计数，防止锁定持续
            existing.FailedLoginCount = 0;
            existing.IsLocked = false;
        }

        existing.UpdatedAt = nowUtc;

        return await _accounts.UpdateAsync(existing);
    }

    /// <summary>
    /// 更新账号启用状态。
    /// </summary>
    public async Task<bool> SetActiveAsync(long userId, bool isActive)
    {
        var rows = await _accounts.AsUpdateable()
            .SetColumns(u => new SysUserAccount { IsActive = isActive })
            .Where(u => u.Id == userId)
            .ExecuteCommandAsync();
        return rows > 0;
    }

    /// <summary>
    /// 重置失败登录次数。
    /// </summary>
    public async Task<bool> ResetFailedLoginCounterAsync(long userId)
    {
        var rows = await _accounts.AsUpdateable()
            .SetColumns(u => new SysUserAccount
            {
                FailedLoginCount = 0,
                IsLocked = false
            })
            .Where(u => u.Id == userId)
            .ExecuteCommandAsync();
        return rows > 0;
    }

    /// <summary>
    /// 记录登录审计。
    /// </summary>
    public Task LogLoginAsync(SysLoginAudit audit)
    {
        ArgumentNullException.ThrowIfNull(audit);
        audit.OccurredAt = audit.OccurredAt == default ? DateTime.UtcNow : audit.OccurredAt;
        return _loginAudits.InsertAsync(audit);
    }

    /// <summary>
    /// 生成密码重置令牌。
    /// </summary>
    public async Task<long> CreatePasswordResetTokenAsync(SysPasswordResetToken token)
    {
        ArgumentNullException.ThrowIfNull(token);
        token.CreatedAt = DateTime.UtcNow;
        return await _resetTokens.InsertReturnIdentityAsync(token);
    }

    /// <summary>
    /// 更新密码重置令牌使用状态。
    /// </summary>
    public async Task<bool> MarkPasswordResetTokenUsedAsync(long tokenId, DateTime usedAtUtc)
    {
        var rows = await _resetTokens.AsUpdateable()
            .SetColumns(t => new SysPasswordResetToken { UsedAt = usedAtUtc })
            .Where(t => t.Id == tokenId && t.UsedAt == null)
            .ExecuteCommandAsync();
        return rows > 0;
    }

    /// <summary>
    /// 创建登录会话。
    /// </summary>
    public async Task<long> CreateSessionAsync(SysUserSession session)
    {
        ArgumentNullException.ThrowIfNull(session);
        session.IssuedAt = session.IssuedAt == default ? DateTime.UtcNow : session.IssuedAt;
        return await _sessions.InsertReturnIdentityAsync(session);
    }

    /// <summary>
    /// 撤销登录会话。
    /// </summary>
    public async Task<bool> RevokeSessionAsync(long sessionId, DateTime revokedAtUtc)
    {
        var rows = await _sessions.AsUpdateable()
            .SetColumns(s => new SysUserSession { RevokedAt = revokedAtUtc })
            .Where(s => s.Id == sessionId && s.RevokedAt == null)
            .ExecuteCommandAsync();
        return rows > 0;
    }

    /// <summary>
    /// 获取用户当前有效会话。
    /// </summary>
    public Task<List<SysUserSession>> GetActiveSessionsAsync(long tenantId, long userId, DateTime nowUtc) =>
        _sessions.AsQueryable()
            .Where(s => s.TenantId == tenantId && s.UserId == userId && s.ExpiresAt > nowUtc && s.RevokedAt == null)
            .ToListAsync();

    /// <summary>
    /// 分配角色，先清空现有关联，再批量写入。
    /// </summary>
    public async Task AssignRolesAsync(long userId, IEnumerable<long> roleIds)
    {
        ArgumentNullException.ThrowIfNull(roleIds);
        var roleList = roleIds.Distinct().ToList();

        await _userRole.AsDeleteable()
            .Where(ur => ur.UserId == userId)
            .ExecuteCommandAsync();

        if (roleList.Count == 0)
        {
            return;
        }

        var now = DateTime.UtcNow;
        var insertData = roleList
            .Select(roleId => new SysUserRole
            {
                UserId = userId,
                RoleId = roleId,
                AssignedAt = now
            })
            .ToList();

        await _userRole.AsInsertable(insertData).ExecuteCommandAsync();
    }

    /// <summary>
    /// 查询登录审计日志。
    /// </summary>
    public Task<List<SysLoginAudit>> GetLoginAuditsAsync(
        long tenantId,
        long? userId = null,
        DateTime? fromUtc = null,
        DateTime? toUtc = null)
    {
        var query = _loginAudits.AsQueryable()
            .Where(a => a.TenantId == tenantId);

        if (userId.HasValue)
        {
            query = query.Where(a => a.UserId == userId.Value);
        }

        if (fromUtc.HasValue)
        {
            query = query.Where(a => a.OccurredAt >= fromUtc.Value);
        }

        if (toUtc.HasValue)
        {
            query = query.Where(a => a.OccurredAt <= toUtc.Value);
        }

        return query.OrderBy(a => a.OccurredAt, OrderByType.Desc).ToListAsync();
    }

    /// <summary>
    /// 分页查询登录审计日志。
    /// </summary>
    public async Task<(List<SysLoginAudit> Items, int TotalItems, int TotalPages)> GetLoginAuditPageAsync(
        long tenantId,
        int page,
        int pageSize,
        long? userId = null,
        DateTime? fromUtc = null,
        DateTime? toUtc = null)
    {
        if (page <= 0)
        {
            throw new ArgumentOutOfRangeException(nameof(page));
        }

        if (pageSize <= 0)
        {
            throw new ArgumentOutOfRangeException(nameof(pageSize));
        }

        RefAsync<int> totalItems = 0;
        var query = _loginAudits.AsQueryable()
            .Where(a => a.TenantId == tenantId);

        if (userId.HasValue)
        {
            query = query.Where(a => a.UserId == userId.Value);
        }

        if (fromUtc.HasValue)
        {
            query = query.Where(a => a.OccurredAt >= fromUtc.Value);
        }

        if (toUtc.HasValue)
        {
            query = query.Where(a => a.OccurredAt <= toUtc.Value);
        }

        var items = await query
            .OrderBy(a => a.OccurredAt, OrderByType.Desc)
            .ToPageListAsync(page, pageSize, totalItems);

        var totalCount = (int)totalItems;
        var totalPages = totalCount == 0 ? 0 : (int)Math.Ceiling(totalCount / (double)pageSize);
        return (items, totalCount, totalPages);
    }
    
    private static (string Hash, string Algorithm) HashPassword(string password)
    {
        if (string.IsNullOrWhiteSpace(password))
        {
            throw new ArgumentException("Password can not be empty.", nameof(password));
        }

        const int iterations = 100_000;
        const int saltSize = 16;
        const int hashSize = 32;
        const string algorithm = "pbkdf2-sha256";

        var salt = RandomNumberGenerator.GetBytes(saltSize);
        var hash = Rfc2898DeriveBytes.Pbkdf2(password, salt, iterations, HashAlgorithmName.SHA256, hashSize);
        var payload = $"{Convert.ToBase64String(salt)}.{Convert.ToBase64String(hash)}.{iterations}";
        return ($"{algorithm}${payload}", algorithm);
    }
}
